﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
namespace ReallyReallyReallySimpleRogueLike
{ 
    class ReallyReallyReallySimpleRogueLike
    {
        private static void Main(string[] args)
        {
            DungeonGameManager dungeonGameManager = new DungeonGameManager();
            string displayText = Constants.IntroductionText;
            while (dungeonGameManager.IsGameActive)
            {
                dungeonGameManager.DrawDungeon();
                DrawDisplayText(displayText);
                displayText = dungeonGameManager.ExecuteCommand(Console.ReadKey());
            }
            dungeonGameManager.DrawDungeon();
            ConcludeGame(dungeonGameManager.PlayerHasWon.Value);
        }

        private static void DrawDisplayText(string displayText)
        {
            Console.WriteLine(displayText);
            Console.Write(Constants.CursorImage);
        }

        private static void ConcludeGame(bool hasPlayerWon)
        {
            string gameConclusionText = (hasPlayerWon) ? Constants.PlayerWinsText : Constants.MonsterWinsText;
            Console.WriteLine(gameConclusionText);
            Console.ReadLine();
        }
    }

    //Refactor 14: Add the new Game Manager Class 
    class DungeonGameManager
    { 
        //Refactor 3:Make the members explicitly private
        private Dungeon dungeon;
        private Player player;           //Refactor 2:Player is now private       
        private IList<Monster> monsters; //Refactor 4:IList replaces List
        private IList<Sword> swords;     //Refactor 4:IList replaces List
        private bool playerAlive;
        private static Random random;

        public DungeonGameManager ()
	    {
            random = new Random();
            playerAlive = true;
            dungeon = new Dungeon(Constants.DungeonWidth, Constants.DungeonHeight);
            player = new Player(GetValidRandomPoint());
            player.CreatureDeadEvent += new Creature.CreatureDeadHandler(player_CreatureDeadEvent);
            monsters = new List<Monster>();
            swords = new List<Sword>();
            
            for (int i = 0; i < Constants.NumberOfMonsters; i++)
            {
                Monster monster = new Monster(GetValidRandomPoint());
                monster.CreatureDeadEvent += new Creature.CreatureDeadHandler(monster_CreatureDeadEvent);
                monsters.Add(monster);
            }

            for (int i = 0; i < Constants.NumberOfSwords; i++)
            {
                Sword sword = new Sword(GetValidRandomPoint());
                swords.Add(sword);
            }
	    }

        public void DrawDungeon()
        {
            SetAllDungeonObjectsToTiles();
            DrawToConsole();
        }

        void monster_CreatureDeadEvent(object sender)
        {
            monsters.Remove((Monster)sender);
        }

        private void player_CreatureDeadEvent(object sender)
        {
            playerAlive = false;
        }

        //Refactor 7:Replace that crazy name of valid move
        private Point GetValidRandomPoint()
        {
            Point p;
            do
            {
                p = new Point(random.Next(Constants.DungeonWidth), random.Next(Constants.DungeonWidth));
            }
            while (!dungeon.IsOKToMove(p));
            return p;
        }

        void player_CreatureDeadEvent()
        {
            playerAlive = false;
        }
        
        public bool? PlayerHasWon
        {
            get { return playerAlive && !IsGameActive; }
        }

        public bool IsGameActive
        {
            //Refactor 5:the alive monster count is now just the monster count as
            //we now remove the monster form the collection when they die
            get { return (player.Hits >0 && monsters.Count() > 0); }
        }

        public string ExecuteCommand(ConsoleKeyInfo command)
        {
            StringBuilder commandResult = new StringBuilder(ProcessCommand(command));
            commandResult.Append(ProcessMonsters());
            SetAllDungeonObjectsToTiles();
            return commandResult.ToString();
        }

        private string ProcessMonsters()
        {
            if (monsters.Count()>0)
            {
                MoveMonsters();
                return FightMonstersOnPlayersTile();
            }
            return string.Empty;
        }

        private string FightMonstersOnPlayersTile()
        {
            var monstersInBattle = monsters.Where(m => m.X == player.X && m.Y == player.Y).ToList();
            string outputText = string.Empty;
            
            if (monstersInBattle.Count() > 0)
            {
                SimpleBattle battle = new SimpleBattle(player, monstersInBattle);
                outputText = battle.Fight();
            }

            return outputText;
        }

        private void MoveMonsters()
        {
            monsters.ToList().ForEach(m =>
            {
                Point currentPlayerLocation = new Point(player.X, player.Y);
                m.MoveToPlayer(currentPlayerLocation);
            });
        }

        private void SetAllDungeonObjectsToTiles()
        {
            dungeon.Clear();
            swords.ToList().ForEach(s=>dungeon.AddSword(s));
            monsters.ToList().ForEach(m => dungeon.AddMonster(m));
            dungeon.walls.ToList().ForEach(w => dungeon.AddWalls(w));
            dungeon.AddPlayer(player);
        }

        public string ProcessCommand(ConsoleKeyInfo command)
        {
            string output = string.Empty;
            switch (command.Key)
            {
                case ConsoleKey.UpArrow:
                case ConsoleKey.DownArrow:
                case ConsoleKey.RightArrow:
                case ConsoleKey.LeftArrow:
                    output = GetNewLocation(command, player);
                    break;
                case ConsoleKey.F1:
                    output = Constants.NoHelpText;
                    break;
            }
            return output;
        }

        private string GetNewLocation(ConsoleKeyInfo command, Player player)
        {
           Constants.Direction direction = GetDirectionFromConsoleKey(command);
           Point newPoint = player.Move(direction);
           if (dungeon.IsOKToMove(newPoint))
            {
                player.X = newPoint.X;
                player.Y = newPoint.Y;
                if (dungeon.GetTile(newPoint) is Sword && player.Inventory.Count == 0)
                {
                    Sword sword = (Sword)dungeon.GetTile(newPoint);
                    player.Inventory.Add(sword);
                    swords.Remove(sword);
                }
                return Constants.OKCommandText;
            }
            else
                return Constants.InvalidMoveText;
        }

        public Constants.Direction GetDirectionFromConsoleKey(ConsoleKeyInfo command)
        {
            Constants.Direction direction = Constants.Direction.Unknown;
            switch(command.Key)
            {
                case ConsoleKey.UpArrow:
                    direction = Constants.Direction.N;
                    break;
                case ConsoleKey.DownArrow:
                    direction = Constants.Direction.S;
                    break;
                case ConsoleKey.RightArrow:
                    direction = Constants.Direction.E;
                    break;
                case ConsoleKey.LeftArrow:
                    direction = Constants.Direction.W;
                    break;
            }
            return direction;
        }

        public void DrawToConsole()
        {
            Console.Clear();
            for (int i = 0; i < Constants.DungeonHeight; i++)
            {
                for (int j = 0; j < Constants.DungeonWidth; j++)
                {
                    Console.ForegroundColor = dungeon.Tiles[j, i].Color;
                    Console.Write(dungeon.Tiles[j, i].ImageCharacter);
                }
                Console.WriteLine();
            }
        }
    }


    class Dungeon
    {
        public IList<Wall> walls;           //Refactor 3: Replace the List<> with IList<>
        public Tile[,] Tiles;               //Refactor 2: make Tiles private

        public enum Direction
        {
            North,
            South,
            East,
            West
        }

        public Dungeon(int xMax, int yMax)
        {
            walls = new List<Wall>();
            Tiles = new Tile[xMax, yMax];
            BuildRandomDungeon();     
        }

        private void SetAllDungeonSquaresToTiles()
        {
            for (int i = 0; i < Constants.DungeonHeight; i++)
            {
                for (int j = 0; j < Constants.DungeonWidth; j++)
                {
                    Tiles[j, i] = new Tile(i, j);
                }
            }
        }
       
        private void BuildRandomDungeon()
        {
            SetAllDungeonSquaresToTiles();

            for (int i = 0; i < Constants.DungeonWidth; i++)
            {
                Wall top = new Wall(i, 0);
                walls.Add(top);
                Wall bottom = new Wall(i, Constants.DungeonHeight - 1);
                walls.Add(bottom);
            }

            for (int i = 0; i < Constants.DungeonHeight; i++)
            {
                Wall left = new Wall(0, i);
                walls.Add(left);
                Wall right = new Wall(Constants.DungeonWidth - 1, i);
                walls.Add(right);
            }    
        }

        //Refactor 9: IsInvalidValidMove has been renamed to IsOKToMove
        //Refactor 10:less than greater than logic to determine a valid move
        public bool IsOKToMove(Point p)
        {
            return ((p.X > 0 && p.X < Constants.DungeonWidth - 1) && (p.Y < Constants.DungeonHeight - 1 && p.Y > 0));
        }

        internal void AddSword(Sword s)
        {
            Tiles[s.X, s.Y] = s;
        }

        internal void AddMonster(Monster m)
        {
            Tiles[m.X, m.Y] = m;
        }

        internal void AddWalls(Wall w)
        {
            Tiles[w.X, w.Y] = w;
        }

        internal void AddPlayer(Player player)
        {
            Tiles[player.X, player.Y] = player;
        }

        internal Tile GetTile(Point point)
        {
            return Tiles[point.X, point.Y];
        }

        internal void Clear()
        {
            SetAllDungeonSquaresToTiles();
        }
    }

    public class Tile
    {
        public string name { get; set; }
        public string ImageCharacter { get; set; }
        public ConsoleColor Color { get; set; }
        public int X { get; set; }
        public int Y { get; set; }

        public Tile() { }

        public Tile(int x, int y)
            : base()
        {
            this.X = x;
            this.Y = y;
            ImageCharacter = Constants.TileImage;
            Color = Constants.TileColor;
        }
    }

    public class Wall : Tile
    {
        public Wall(int x, int y)
            : base(x, y)
        {
            ImageCharacter = Constants.WallImage;
            this.Color = Constants.WallColor;
        }
    }

    public class Sword : Tile
    {
        public Sword(Point p)
        {
            ImageCharacter = Constants.SwordImage;
            this.Color = Constants.SwordColor;
            X = p.X;
            Y = p.Y;
        }
    }

    public class Creature : Tile
    {
        private int hits;

        public delegate void CreatureDeadHandler(Object sender);
        public event CreatureDeadHandler CreatureDeadEvent;

        public int Hits
        {
            get
            {
                return hits;
            }
           set
           { 
               hits = value;
               if (hits <= 0)
               {
                   if (CreatureDeadEvent != null)
                   {
                       CreatureDeadEvent(this);
                   }
               }
           }
        }

        public bool Alive
        {
            get { return (Hits >= 0);}
        }

        public void Attack(Creature creature)
        {
            creature.Hits= 0;
        }
    }

    public class Player : Creature
    {
        public Player(Point p)
        {
            ImageCharacter = Constants.PlayerImage;
            Color = Constants.PlayerColor;
            Inventory = new List<Sword>();
            X = p.X;
            Y = p.Y;
            Hits = Constants.StartingHitPoints;
        }

        //Refactor 6: Encapsulate the move action within the player class 
        public Point Move(Constants.Direction direction)
        {
            Point newPoint = new Point(X, Y);
            switch (direction)
            {
                case Constants.Direction.N:
                    newPoint.Y -= 1;
                    break;
                case Constants.Direction.S:
                    newPoint.Y += 1;
                    break;
                case Constants.Direction.E:
                    newPoint.X += 1;
                    break;
                case Constants.Direction.W:
                    newPoint.X -= 1;
                    break;
            }
            return newPoint;
        }

        public IList<Sword> Inventory { get; set; }
    }

    public class Monster : Creature
    {
        public Monster(Point p)
        {
            ImageCharacter = Constants.MonsterImage;
            Color = Constants.MonsterColor;
            X = p.X;
            Y = p.Y;
            Hits = Constants.StartingHitPoints;
        }

        //Refactor 13:MoveMonsterToPlayer. The monster should be in charge of where it moves.
        public void MoveToPlayer(Point playerLocation)
        {
            Point move = new Point(X, Y);
            if (X > 0 && X < Constants.DungeonWidth && (X != playerLocation.X))
                X += (X > playerLocation.X) ? -1 : 1;

            if ((Y > 0 && Y < Constants.DungeonHeight) && (Y != playerLocation.Y))
                Y += (Y > playerLocation.Y) ? -1 : 1;

        } 
    }

    //Refactor 12:Introduce a battle class to encapsulate the way a battle occurs - this also includes IBattle
    public class SimpleBattle : IBattle
    {
        private Player player;
        private IList<Monster> monsters;

        public SimpleBattle(Player player,IList<Monster> monsters)
        {
            this.player = player;
            this.monsters = monsters;
        }

        public string Fight()
        {
            Monster monster = monsters.OrderBy(m => m.Hits).FirstOrDefault();//get the monster with the most hits
            if (player.Inventory.Any<Sword>())
            {
                player.Attack(monster);
                return Constants.PlayerKillsMonsterText;
            }
            else
            {
                monster.Attack(player);
                return Constants.MonsterKillsPlayerText;
            }
        }
    }

    public interface IBattle
    {
        string Fight();
    }

    public static class Constants
    {
        public readonly static int DungeonHeight = 20;
        public readonly static int DungeonWidth = 20;
        public readonly static int NumberOfSwords = 10;
        public readonly static int MonsterDamage = 2;
        public readonly static int NumberOfMonsters = 3;
        public readonly static int StartingHitPoints = 10;

        public readonly static string TileImage = ".";
        public readonly static string WallImage = "#";
        public readonly static string PlayerImage = "@";
        public readonly static string SwordImage = "s";
        public readonly static string StepsImage = "S";
        public readonly static string MonsterImage = "M";
        public readonly static string CursorImage = ">";

        public readonly static ConsoleColor MonsterColor = ConsoleColor.Blue;
        public readonly static ConsoleColor PlayerColor = ConsoleColor.Gray;
        public readonly static ConsoleColor WallColor = ConsoleColor.DarkCyan;
        public readonly static ConsoleColor SwordColor = ConsoleColor.Yellow;
        public readonly static ConsoleColor TileColor = ConsoleColor.White;

        public readonly static string InvalidCommandText = "That is not a valid command";
        public readonly static string OKCommandText = "OK";
        public readonly static string InvalidMoveText = "That is not a valid move";
        public readonly static string IntroductionText = "Welcome to the dungeon - grab a sword kill the monster(s) win the game";
        public readonly static string PlayerWinsText = "Player kills monster and wins";
        public readonly static string MonsterWinsText = "Monster wins";
        public readonly static string NoHelpText = "No help text";
        public readonly static string PlayerKillsMonsterText = "Player kills a monster";
        public readonly static string MonsterKillsPlayerText = "Monster kills a player";

        public enum Direction { N, S, E, W, Unknown };
    }
}

